/*-------------------------------------------------------------------------
 *
 * heapam_xlog.h
 *	  POSTGRES 堆访问 XLOG 定义。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/access/heapam_xlog.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef HEAPAM_XLOG_H
#define HEAPAM_XLOG_H

#include "access/htup.h"
#include "access/xlogreader.h"
#include "lib/stringinfo.h"
#include "storage/buf.h"
#include "storage/bufpage.h"
#include "storage/relfilenode.h"
#include "utils/relcache.h"


/*
 * heapam.c 的 WAL 操作的 WAL 记录定义
 *
 * XLOG 允许在日志记录 xl_info 字段的高 4 位中存储一些信息。
 * 我们使用 3 位作为操作码，1 位作为初始化位。
 */
#define XLOG_HEAP_INSERT		0x00
#define XLOG_HEAP_DELETE		0x10
#define XLOG_HEAP_UPDATE		0x20
#define XLOG_HEAP_TRUNCATE		0x30
#define XLOG_HEAP_HOT_UPDATE	0x40
#define XLOG_HEAP_CONFIRM		0x50
#define XLOG_HEAP_LOCK			0x60
#define XLOG_HEAP_INPLACE		0x70

#define XLOG_HEAP_OPMASK		0x70
/*
 * 当我们在 INSERT、UPDATE、HOT_UPDATE
 * 或 MULTI_INSERT 中在新页面上插入第一个条目时，我们可以（并且我们确实这样做）在重做中恢复整个页面
 */
#define XLOG_HEAP_INIT_PAGE		0x80
/*
 * 我们用完了操作码，因此heapam.c现在有了第二个RmgrId。这些操作码
 * 与RM_HEAP2_ID相关，但在逻辑上与上面与RM_HEAP_ID相关的操作码并无不同。
 * XLOG_HEAP_OPMASK同样适用于这些操作码。
 */
#define XLOG_HEAP2_REWRITE		0x00
#define XLOG_HEAP2_PRUNE		0x10
#define XLOG_HEAP2_VACUUM		0x20
#define XLOG_HEAP2_FREEZE_PAGE	0x30
#define XLOG_HEAP2_VISIBLE		0x40
#define XLOG_HEAP2_MULTI_INSERT 0x50
#define XLOG_HEAP2_LOCK_UPDATED 0x60
#define XLOG_HEAP2_NEW_CID		0x70

/*
 * xl_heap_insert/xl_heap_multi_insert标志值，8位可用。
 */
/* PD_ALL_VISIBLE被清除 */
#define XLH_INSERT_ALL_VISIBLE_CLEARED			(1<<0)
#define XLH_INSERT_LAST_IN_MULTI				(1<<1)
#define XLH_INSERT_IS_SPECULATIVE				(1<<2)
#define XLH_INSERT_CONTAINS_NEW_TUPLE			(1<<3)
#define XLH_INSERT_ON_TOAST_RELATION			(1<<4)

/* all_frozen_set总是意味着all_visible_set */
#define XLH_INSERT_ALL_FROZEN_SET				(1<<5)

/*
 * xl_heap_update标志值，8位可用。
 */
/* PD_ALL_VISIBLE被清除 */
#define XLH_UPDATE_OLD_ALL_VISIBLE_CLEARED		(1<<0)
/* 在第二页中PD_ALL_VISIBLE被清除 */
#define XLH_UPDATE_NEW_ALL_VISIBLE_CLEARED		(1<<1)
#define XLH_UPDATE_CONTAINS_OLD_TUPLE			(1<<2)
#define XLH_UPDATE_CONTAINS_OLD_KEY				(1<<3)
#define XLH_UPDATE_CONTAINS_NEW_TUPLE			(1<<4)
#define XLH_UPDATE_PREFIX_FROM_OLD				(1<<5)
#define XLH_UPDATE_SUFFIX_FROM_OLD				(1<<6)

/* 方便宏，用于检查是否记录了任何形式的旧元组 */
#define XLH_UPDATE_CONTAINS_OLD						\
	(XLH_UPDATE_CONTAINS_OLD_TUPLE | XLH_UPDATE_CONTAINS_OLD_KEY)

/*
 * xl_heap_delete标志值，8位可用。
 */
/* PD_ALL_VISIBLE被清除 */
#define XLH_DELETE_ALL_VISIBLE_CLEARED			(1<<0)
#define XLH_DELETE_CONTAINS_OLD_TUPLE			(1<<1)
#define XLH_DELETE_CONTAINS_OLD_KEY				(1<<2)
#define XLH_DELETE_IS_SUPER						(1<<3)
#define XLH_DELETE_IS_PARTITION_MOVE			(1<<4)

/* 方便宏，用于检查是否记录了任何形式的旧元组 */
#define XLH_DELETE_CONTAINS_OLD						\
	(XLH_DELETE_CONTAINS_OLD_TUPLE | XLH_DELETE_CONTAINS_OLD_KEY)

/* 这是我们需要知道的关于删除的内容 */
typedef struct xl_heap_delete
{
	TransactionId xmax;			/* 被删除的元组的xmax */
	OffsetNumber offnum;		/* 被删除元组的偏移量 */
	uint8		infobits_set;	/* 信息掩码位 */
	uint8		flags;
} xl_heap_delete;

#define SizeOfHeapDelete	(offsetof(xl_heap_delete, flags) + sizeof(uint8))

/*
 * xl_heap_truncate标志值，8位可用。
 */
#define XLH_TRUNCATE_CASCADE					(1<<0)
#define XLH_TRUNCATE_RESTART_SEQS				(1<<1)

/*
 * 对于截断，我们在一个数组中列出所有被截断的relid，然后是所有
 * 需要重启的序列relid（如果有）。
 * 所有rel总是在同一个数据库中，因此我们只需列出一次dbid。
 */
typedef struct xl_heap_truncate
{
	Oid			dbId;
	uint32		nrelids;
	uint8		flags;
	Oid			relids[FLEXIBLE_ARRAY_MEMBER];
} xl_heap_truncate;

#define SizeOfHeapTruncate	(offsetof(xl_heap_truncate, relids))

/*
 * 我们不会在WAL中存储已插入或已更新元组的整个固定部分（HeapTupleHeaderData）;
 * 我们可以通过重构在WAL记录的其他地方可用的字段，或者可能仅仅是
 * 不需要重构来节省几个字节。这些是我们必须存储的字段。
 */
typedef struct xl_heap_header
{
	uint16		t_infomask2;
	uint16		t_infomask;
	uint8		t_hoff;
} xl_heap_header;

#define SizeOfHeapHeader	(offsetof(xl_heap_header, t_hoff) + sizeof(uint8))

/* 这是我们需要知道的关于插入的内容 */
typedef struct xl_heap_insert
{
	OffsetNumber offnum;		/* 插入元组的偏移量 */
	uint8		flags;

	/* xl_heap_header & TUPLE DATA在备份块0中 */
} xl_heap_insert;

#define SizeOfHeapInsert	(offsetof(xl_heap_insert, flags) + sizeof(uint8))

/*
 * 这是我们需要知道的关于批量插入的内容。
 *
 * 记录的主要数据由这个xl_heap_multi_insert头部组成。
 * 如果整个页面被重新初始化（XLOG_HEAP_INIT_PAGE），则省略'offsets'数组。
 *
 * 在块0的数据部分，有一个xl_multi_insert_tuple结构，
 * 后面跟着每个元组的元组数据。为了对齐每个xl_multi_insert_tuple结构，
 * 还有填充。
 */
typedef struct xl_heap_multi_insert
{
	uint8		flags;
	uint16		ntuples;
	OffsetNumber offsets[FLEXIBLE_ARRAY_MEMBER];
} xl_heap_multi_insert;

#define SizeOfHeapMultiInsert	offsetof(xl_heap_multi_insert, offsets)

typedef struct xl_multi_insert_tuple
{
	uint16		datalen;		/* 后续的元组数据大小 */
	uint16		t_infomask2;
	uint16		t_infomask;
	uint8		t_hoff;
	/* 元组数据在结构末尾跟随 */
} xl_multi_insert_tuple;

#define SizeOfMultiInsertTuple	(offsetof(xl_multi_insert_tuple, t_hoff) + sizeof(uint8))

/*
 * 这是我们需要知道的关于更新|热更新的内容
 *
 * 备份块0：新页面
 *
 * 如果设置了XLH_UPDATE_PREFIX_FROM_OLD或XLH_UPDATE_SUFFIX_FROM_OLD标志，
 * 前缀和/或后缀将作为一个或两个uint16s放在前面。
 *
 * 然后，xl_heap_header和新元组数据跟随。新的元组
 * 数据不包括前缀和后缀，这些是在重放时从
 * 旧元组复制过来的。
 *
 * 如果给定XLH_UPDATE_CONTAINS_NEW_TUPLE标志，即使进行了全页图像
 * 捕获，元组数据也会被包括。
 *
 * 备份块1：旧页面（如果不同）。(没有数据，只是对块的引用)
 */
typedef struct xl_heap_update
{
	TransactionId old_xmax;		/* 旧元组的xmax */
	OffsetNumber old_offnum;	/* 旧元组的偏移量 */
	uint8		old_infobits_set;	/* 要在旧元组上设置的信息掩码位 */
	uint8		flags;
	TransactionId new_xmax;		/* 新元组的xmax */
	OffsetNumber new_offnum;	/* 新元组的偏移量 */

	/*
	 * 如果设置了XLH_UPDATE_CONTAINS_OLD_TUPLE或XLH_UPDATE_CONTAINS_OLD_KEY标志，
	 * 则xl_heap_header和旧元组的元组数据将跟随。
	 */
} xl_heap_update;

#define SizeOfHeapUpdate	(offsetof(xl_heap_update, new_offnum) + sizeof(OffsetNumber))

/*
 * 这是我们需要知道的关于页面修剪的内容（在VACUUM期间和
 * 机会性修剪期间）
 *
 * 在记录的固定部分之后，OffsetNumbers数组包含：
 *	* 对于每个重定向的项：项目偏移量，然后是重定向到的偏移量
 *	* 对于每个现已死亡的项：项目偏移量
 *	* 对于每个现已未使用的项：项目偏移量
 * 因此OffsetNumbers的总数为2*nredirected + ndead + nunused。
 * 注意，nunused并未显式存储，但可以通过参考
 * 总记录长度找到。
 *
 * 获取完整的清理锁。
 */
typedef struct xl_heap_prune
{
	TransactionId latestRemovedXid;
	uint16		nredirected;
	uint16		ndead;
	/* 偏移数字在块引用 0 中 */
} xl_heap_prune;

#define SizeOfHeapPrune (offsetof(xl_heap_prune, ndead) + sizeof(uint16))

/*
 * 清理页面记录类似于修剪记录，但仅能标记
 * 已经是 LP_DEAD 的项为 LP_UNUSED（在清理的第二次访问堆时）
 *
 * 仅获取普通排他锁。
 */
typedef struct xl_heap_vacuum
{
	uint16		nunused;
	/* 偏移数字在块引用 0 中 */
} xl_heap_vacuum;

#define SizeOfHeapVacuum (offsetof(xl_heap_vacuum, nunused) + sizeof(uint16))

/* infobits_set 的标志 */
#define XLHL_XMAX_IS_MULTI		0x01
#define XLHL_XMAX_LOCK_ONLY		0x02
#define XLHL_XMAX_EXCL_LOCK		0x04
#define XLHL_XMAX_KEYSHR_LOCK	0x08
#define XLHL_KEYS_UPDATED		0x10

/* xl_heap_lock / xl_heap_lock_updated 的标志字段中的标志位 */
#define XLH_LOCK_ALL_FROZEN_CLEARED		0x01

/* 这是我们需要了解的关于锁的信息 */
typedef struct xl_heap_lock
{
	TransactionId locking_xid;	/* 可能是 MultiXactId 而不是 xid */
	OffsetNumber offnum;		/* 锁定元组在页面上的偏移量 */
	int8		infobits_set;	/* 要设置的 infomask 和 infomask2 位 */
	uint8		flags;			/* XLH_LOCK_* 标志位 */
} xl_heap_lock;

#define SizeOfHeapLock	(offsetof(xl_heap_lock, flags) + sizeof(int8))

/* 这是我们需要了解的关于锁定行的更新版本的信息 */
typedef struct xl_heap_lock_updated
{
	TransactionId xmax;
	OffsetNumber offnum;
	uint8		infobits_set;
	uint8		flags;
} xl_heap_lock_updated;

#define SizeOfHeapLockUpdated	(offsetof(xl_heap_lock_updated, flags) + sizeof(uint8))

/* 这是我们需要了解的关于确认投机插入的信息 */
typedef struct xl_heap_confirm
{
	OffsetNumber offnum;		/* 确认的元组在页面上的偏移量 */
} xl_heap_confirm;

#define SizeOfHeapConfirm	(offsetof(xl_heap_confirm, offnum) + sizeof(OffsetNumber))

/* 这是我们需要了解的关于就地更新的信息 */
typedef struct xl_heap_inplace
{
	OffsetNumber offnum;		/* 更新的元组在页面上的偏移量 */
} xl_heap_inplace;

#define SizeOfHeapInplace	(offsetof(xl_heap_inplace, offnum) + sizeof(OffsetNumber))

/*
 * 此结构表示 '冻结计划'，这是我们需要了解的关于
 * 在清理期间冻结单个元组的信息。
 */
/* 0x01 是 XLH_FREEZE_XMIN */
#define		XLH_FREEZE_XVAC		0x02
#define		XLH_INVALID_XVAC	0x04

typedef struct xl_heap_freeze_tuple
{
	TransactionId xmax;
	OffsetNumber offset;
	uint16		t_infomask2;
	uint16		t_infomask;
	uint8		frzflags;
} xl_heap_freeze_tuple;

/*
 * 这是我们需要了解的关于在清理期间冻结一个块的信息
 *
 * 备份块 0 的数据包含一个 xl_heap_freeze_tuple 结构的数组，
 * 每个元组一个。
 */
typedef struct xl_heap_freeze_page
{
	TransactionId cutoff_xid;
	uint16		ntuples;
} xl_heap_freeze_page;

#define SizeOfHeapFreezePage (offsetof(xl_heap_freeze_page, ntuples) + sizeof(uint16))

/*
 * 这是我们需要了解的关于设置可见性映射位的信息
 *
 * 备份块 0：可见性映射缓冲区
 * 备份块 1：堆缓冲区
 */
typedef struct xl_heap_visible
{
	TransactionId cutoff_xid;
	uint8		flags;
} xl_heap_visible;

#define SizeOfHeapVisible (offsetof(xl_heap_visible, flags) + sizeof(uint8))

typedef struct xl_heap_new_cid
{
	/*
	 * 存储顶层 xid，以便我们不必合并来自不同
	 * 事务的 cids
	 */ 
	TransactionId top_xid;
	CommandId	cmin;
	CommandId	cmax;
	CommandId	combocid;		/* 仅用于调试 */

	/*
	 * 存储 relfilenode/ctid 对以方便查找。
	 */
	RelFileNode target_node;
	ItemPointerData target_tid;
} xl_heap_new_cid;

#define SizeOfHeapNewCid (offsetof(xl_heap_new_cid, target_tid) + sizeof(ItemPointerData))

/* 逻辑重写 xlog 记录头 */
typedef struct xl_heap_rewrite_mapping
{
	TransactionId mapped_xid;	/* 可能需要查看行的xid */
	Oid			mapped_db;		/* 共享关系的 DbOid 或 InvalidOid */
	Oid			mapped_rel;		/* 映射关系的 Oid */
	off_t		offset;			/* 到目前为止我们已写入的字节数 */
	uint32		num_mappings;	/* 内存中映射的数量 */
	XLogRecPtr	start_lsn;		/* 在重写开始处插入 LSN */
} xl_heap_rewrite_mapping;

extern void HeapTupleHeaderAdvanceLatestRemovedXid(HeapTupleHeader tuple,
												   TransactionId *latestRemovedXid);

extern void heap_redo(XLogReaderState *record);
extern void heap_desc(StringInfo buf, XLogReaderState *record);
extern const char *heap_identify(uint8 info);
extern void heap_mask(char *pagedata, BlockNumber blkno);
extern void heap2_redo(XLogReaderState *record);
extern void heap2_desc(StringInfo buf, XLogReaderState *record);
extern const char *heap2_identify(uint8 info);
extern void heap_xlog_logical_rewrite(XLogReaderState *r);

extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer,
								  TransactionId cutoff_xid, xl_heap_freeze_tuple *tuples,
								  int ntuples);
extern bool heap_prepare_freeze_tuple(HeapTupleHeader tuple,
									  TransactionId relfrozenxid,
									  TransactionId relminmxid,
									  TransactionId cutoff_xid,
									  TransactionId cutoff_multi,
									  xl_heap_freeze_tuple *frz,
									  bool *totally_frozen,
									  TransactionId *relfrozenxid_out,
									  MultiXactId *relminmxid_out);
extern void heap_execute_freeze_tuple(HeapTupleHeader tuple,
									  xl_heap_freeze_tuple *xlrec_tp);
extern XLogRecPtr log_heap_visible(RelFileNode rnode, Buffer heap_buffer,
								   Buffer vm_buffer, TransactionId cutoff_xid, uint8 flags);

#endif							/* HEAPAM_XLOG_H */
